package ffmpeg

import (
	"errors"
	"fmt"
	"math"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

type (
	// ClipVideoTime 剪辑视频时间属性
	ClipVideoTime struct {
		Start  int    // 裁剪开始时间
		End    int    // 裁剪结束时间
		Suffix string // 裁剪子视频索引
	}

	// ClipResult 剪辑结果
	ClipResult struct {
		Index   int    // 索引
		OutFile string // 剪辑后输出地址
		Err     error  // 结果
	}

	// CustomClipVideo 自定义视频剪辑
	CustomClipVideo struct {
		Src        string          // 原视频路径
		VideoTimes []ClipVideoTime // 剪辑视频时间格式
		IsMerge    bool            // 是否合并：false否，true合并
		IsGen      bool            // 是否重新生成文件:false直接返回原地址，true生成新的
	}
)

// AverageClipVideos 按视频个数平均剪辑视频
func (f *SFFmpeg) AverageClipVideos(src string, avg int, isGens ...bool) ([]string, error) {
	var (
		totalDuration = f.Duration(src)     // 获取总时长
		second        = totalDuration / avg // 剪辑切片时长（S）
	)
	// 剪辑视频
	return f.BatchClipVideos(src, second, totalDuration, isGens...)
}

// BatchClipVideosBySecond 按间隔秒数批量剪辑视频
func (f *SFFmpeg) BatchClipVideosBySecond(src string, second int, isGens ...bool) ([]string, error) {
	// 批量剪辑视频
	return f.BatchClipVideos(src, second, f.Duration(src), isGens...)
}

// BatchClipVideos 批量剪辑视频
func (f *SFFmpeg) BatchClipVideos(src string, second, totalDuration int, isGens ...bool) (clipUrls []string, err error) {
	// 计算剪辑切片视频时间列表
	clipVideos, err := f.calculateClipVideoTime(second, totalDuration)
	if err != nil {
		return nil, err
	}
	return f.ClipVideos(src, clipVideos, isGens...)
}

// CustomClipVideos 自定义剪辑视频列表
func (f *SFFmpeg) CustomClipVideos(clipVideos ...*CustomClipVideo) error {
	if len(clipVideos) == 0 || (len(clipVideos) > 0 && clipVideos[0] == nil) {
		return errors.New(`缺少剪辑视频参数`)
	}
	var (
		errs = make(chan error, len(clipVideos))
		wg   = sync.WaitGroup{}
	)
	// 开启协程批量剪辑
	for _, clipVideo := range clipVideos {
		wg.Add(1)
		go func(clipVideo CustomClipVideo) {
			defer wg.Done()
			clipUrls, err := f.ClipVideos(clipVideo.Src, clipVideo.VideoTimes, clipVideo.IsGen)
			// 收集错误信息
			if err != nil {
				errs <- err
			}
			fmt.Println(`剪辑成功地址`, clipUrls)
		}(*clipVideo)
	}
	// 等待结束
	wg.Wait()
	close(errs)
	// 错误结果集
	if len(errs) > 0 {
		var messages []string
		for err := range errs {
			messages = append(messages, err.Error())
		}
		return fmt.Errorf(`剪辑失败结果集%s`, strings.Join(messages, `,`))
	}
	return nil
}

// ClipVideos 批量剪辑视频
func (f *SFFmpeg) ClipVideos(src string, clipVideos []ClipVideoTime, isGens ...bool) (clipUrls []string, err error) {
	if len(clipVideos) == 0 {
		return nil, errors.New(`缺少视频剪辑时间`)
	}
	// 初始化
	var (
		// 设置时间，00:00:00开始
		t       = time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Now().Location())
		wg      = sync.WaitGroup{}
		results = make(chan ClipResult, len(clipVideos)) // 错误通道
		errs    []string
		isGen   = len(isGens) == 0 || (len(isGens) > 0 && !isGens[0]) // 检查到视频文件已生成，是否重新新文件：false否，true是
	)
	// 初始化
	clipUrls = make([]string, len(clipVideos))

	// 开启协程迭代剪辑
	for key, item := range clipVideos {
		wg.Add(1)
		go func(item ClipVideoTime, src string, t time.Time, key int, isGen bool) {
			defer wg.Done()
			result := ClipResult{
				Index: key,
			}
			// 剪辑视频
			result.OutFile, result.Err = f.ClipVideo(
				src,
				t.Add(time.Duration(item.Start)*time.Second),
				t.Add(time.Duration(item.End)*time.Second),
				item.Suffix,
				isGen,
			)
			// 结果入队
			results <- result
		}(item, src, t, key, isGen)
	}
	// 等待进程结束
	wg.Wait()
	close(results)

	// 处理剪辑结果
	for result := range results {
		if result.Err != nil {
			errs = append(errs, result.Err.Error())
			continue
		}
		// 组装剪辑结果
		clipUrls[result.Index] = result.OutFile
	}

	// 是否含有失败属性
	if len(errs) > 0 {
		err = fmt.Errorf(`剪辑失败内容：%s`, strings.Join(errs, `,`))
	}
	return clipUrls, err
}

// ClipVideo 剪辑视频
func (f *SFFmpeg) ClipVideo(src string, start, end time.Time, suffix string, isGen bool) (outFile string, err error) {
	// 视频地址
	if !f.IsFileExists(src) {
		return "", errors.New(`原视频地址不存在`)
	}
	// 转换为 FFmpeg 所需的时间格式
	var (
		startTimeStr = start.Format(time.TimeOnly) // 截取开始时间
		endTimeStr   = end.Format(time.TimeOnly)   // 截取结束时间
		dir, file    = filepath.Split(src)         // 存储路径
		filenames    = strings.Split(file, `.`)    // 截取文件名和后缀
	)
	fmt.Println(startTimeStr, `-`, endTimeStr)

	// 检验并创建目录
	dir += filenames[0]
	if err = f.EnsureDir(dir); err != nil {
		return ``, err
	}

	// 输出视频地址
	outFile = filepath.Join(dir, fmt.Sprintf(`%s_%s`, filenames[0], suffix))
	ext := `.` + filenames[len(filenames)-1] // 文件后缀
	// 文件地址是否存在
	if f.IsFileExists(outFile + ext) {
		// 是否重新生成新的文件:false生成新的，true直接返回原视频
		if isGen {
			return outFile + ext, nil
		}
		outFile += fmt.Sprintf(`_%d`, time.Now().Unix())
	}
	// 补充后缀
	outFile += ext

	// 执行命令
	cmd := exec.Command(
		f.Driver,
		"-i", src,
		"-ss", startTimeStr, "-to", endTimeStr,
		"-c:v", "copy", "-c:a", "copy", outFile,
	)
	// 运行输出
	if err = f.executeCommand(cmd); err != nil {
		fmt.Println("Error processing video segment:", err)
		return ``, err
	}
	return outFile, nil
}

// calculateClipVideoTime 计算视频剪辑切片时长
func (f *SFFmpeg) calculateClipVideoTime(second, totalDuration int) ([]ClipVideoTime, error) {
	if totalDuration <= 0 || second <= 0 {
		return nil, fmt.Errorf(`总时长[%d]或剪辑切片时间[%d]不能小于等于0`, totalDuration, second)
	}
	var (
		total         = int(math.Ceil(float64(totalDuration / second))) // 剪辑总分片
		clipDurations = make([]ClipVideoTime, 0)
	)
	// 处理分片
	for i := 1; i <= total; i++ {
		clipVideo := ClipVideoTime{
			Start:  (i - 1) * second,
			Suffix: fmt.Sprintf(`%03d`, i),
		}
		clipVideo.End = clipVideo.Start + second // 裁剪时间
		clipDurations = append(clipDurations, clipVideo)
	}
	// 最后一个切片结束时间，以视频总时长为主
	clipDurations[total-1].End = totalDuration
	return clipDurations, nil
}
